from fastapi import APIRouter, Depends, UploadFile, Form
from fastapi.responses import StreamingResponse, FileResponse
import config
from db import get_db, crud
from utils.response import HTTPException, JSONResponse
from schemas.images import UploadImage, ImageModel, ImagePagination, ImageFolder, \
    CreateImageFolder, UpdateImageFolder, UpdateImageTags
from sqlalchemy.orm import Session
from typing import List, Dict, Optional
from config import FilePath, ImageConf
from PIL import Image
from io import BytesIO
import utils
from utils.images import SVG
import filetype

router = APIRouter(prefix="/images", tags=["图库"])

ContentType = {
    "png": "image/png",
    "jpeg": "image/jpeg",
    "jpg": "image/jpeg",
    "svg": "image/svg+xml",
    "gif": "image/gif",
    "webp": "image/webp"
}

ALLOW_IMAGE_TYPES = ["image/jpeg", "image/png", "image/webp", "image/gif", "image/svg+xml", "image/apng"]


def get_image_url(folder: int, _hash: str, _format: str) -> str:
    return f"/static/images/{folder}/{_hash}.{_format}"


def get_image_thumb_url(folder: int, _hash: str, _format: str) -> str:
    if _format == 'webp':
        return f"/static/images/{folder}/thumb/{_hash}.{_format}"
    else:
        return f"/static/images/{folder}/{_hash}.{_format}"


# 获取图片
@router.get("", response_model=ImagePagination)
def gets(folder: int, page: int, per_page: int, db: Session = Depends(get_db)):
    count, images = crud.Images.gets(db, page - 1, per_page, folder)

    return ImagePagination(
        page=page,
        per_page=per_page,
        folder=folder,
        total=count,
        images=[{
            "id": item.id,
            "width": item.width,
            "height": item.height,
            "url": get_image_url(item.folder, item.hash, item.format),
            "thumb": get_image_thumb_url(item.folder, item.hash, item.format)
        } for item in images]
    )


# 获取图片详情
@router.get("/detail/{id}", response_model=Dict)
def get_image_detail(id: int, db: Session = Depends(get_db)):
    image = crud.Images.get(db, id)
    if image is None:
        raise HTTPException(http_code=404, message="未找到图片")
    else:
        res = image.dict
        res["url"] = get_image_url(image.folder, image.hash, image.format)
        res["thumb"] = get_image_thumb_url(image.folder, image.hash, image.format)
        return JSONResponse(content=res)


# 下载图片
@router.get("/download", response_class=StreamingResponse)
def download(id: int, image_format: str = "webp", db: Session = Depends(get_db)):
    # TODO: 是否需要支持图像大小调整
    img = crud.Images.get(db, id)

    if img is None:
        raise HTTPException(message=f"未找到ID为[{id}]的图片")

    if img.format in ["gif", "svg"]:
        return FileResponse(FilePath.IMAGES / f"{img.folder}/{img.hash}.{img.format}")

    if image_format not in ["png", "jpg", "jpeg", "webp"]:
        raise HTTPException(message=f"不支持转换为[{image_format}]格式")

    if image_format == "webp":
        return FileResponse(FilePath.IMAGES / f"{img.folder}/{img.hash}.webp")
    else:
        if image_format == "jpg":
            image_format = "jpeg"
        img_bytes = BytesIO()
        with open(FilePath.IMAGES / f"{img.folder}/{img.hash}.webp", mode="rb") as fp:
            image = Image.open(fp)
            image.save(img_bytes, format=image_format)
        img_bytes.seek(0)
        return StreamingResponse(img_bytes, media_type=ContentType[image_format])


# 上传图片
@router.post("/upload", response_model=ImageModel, response_model_exclude_none=True)
async def upload_images(file: UploadFile,
                        tags: str = Form(None),
                        folder: int = Form(config.SYSTEM_IMAGE_FOLDER),
                        db: Session = Depends(get_db)):
    # 图片原始数据
    original_data = await file.read()
    # 图片标签
    image_tags = [] if tags is None else tags.split(",")

    # 检查图片类型
    if filetype.is_image(original_data):
        kind = filetype.image_match(original_data)
        if kind is None or kind.mime not in ALLOW_IMAGE_TYPES:
            raise HTTPException(message="不支持此类型图像")

        img = Image.open(BytesIO(original_data))
        img_width = img.width
        img_height = img.height
        # 如果是gif格式直接保存，不进行转换
        if kind.mime == "image/gif":
            img_data = original_data
            img_format = "gif"
        else:
            img_bytes_io = BytesIO()
            img.save(img_bytes_io, format="webp", quality=ImageConf.Quality)
            img_data = img_bytes_io.getvalue()
            img_format = "webp"

        # 计算图片哈希值
        img_hash = utils.files.get_hash_by_bytes(img_data)
        # 查询图片是否存在
        exist = crud.Images.get_by_hash(db, folder, img_hash)

        if exist is None:
            # 保存图片
            utils.files.save_file_by_bytes(data=img_data,
                                           path=FilePath.IMAGES / str(folder),
                                           name=img_hash,
                                           suffix=img_format)
            if img_format == 'webp':
                # 保存预览图
                thumb_bytes_io = BytesIO()
                thumb = img.resize((
                    ImageConf.ThumbWidth,
                    int(ImageConf.ThumbWidth / img_width * img_height)
                ))
                thumb.save(thumb_bytes_io, format="webp", quality=ImageConf.Quality)
                utils.files.save_file_by_bytes(data=thumb_bytes_io.getvalue(),
                                               path=FilePath.IMAGES / f"{folder}/thumb",
                                               name=img_hash,
                                               suffix=img_format)

            return crud.Images.create(db, UploadImage(
                tags=image_tags,
                folder=folder,
                width=img_width,
                height=img_height,
                size=len(img_data),
                hash=img_hash,
                format=img_format
            ))
        else:
            return exist

    elif utils.images.is_svg(original_data):
        svg = SVG(original_data, optimize=True)
        # 计算图片哈希值
        img_hash = svg.hash
        # 查询图片是否存在
        exist = crud.Images.get_by_hash(db, folder, img_hash)
        if exist is None:
            # 保存图片
            svg.save(path=FilePath.IMAGES / str(folder), name=img_hash)
            return crud.Images.create(db, UploadImage(
                tags=image_tags,
                folder=folder,
                width=svg.width,
                height=svg.height,
                size=svg.size,
                hash=img_hash,
                format="svg"
            ))
        else:
            return exist

    else:
        raise HTTPException(message="不支持此类型图像")


# 更新标签
@router.patch("", response_model=Dict)
def update_tags(data: UpdateImageTags, db: Session = Depends(get_db)):
    try:
        image = crud.Images.update_tags(db, data)
        res = image.dict
        res["url"] = f"/static/images/{image.folder}/{image.hash}.{image.format}"
        return JSONResponse(content=res)
    except Exception as err:
        raise HTTPException(message=str(err))


# 删除图片
@router.delete("/{id}")
def delete(id: int, db: Session = Depends(get_db)):
    img = crud.Images.get(db, id)
    if img is None:
        raise HTTPException(message=f"未找到ID为[{id}]的图片")
    file = FilePath.IMAGES / f"{img.folder}/{img.hash}.{img.format}"
    if file.exists():
        file.unlink()
    return crud.Images.delete(db, id)


# 搜索图片
@router.get("/search", response_model=List[Dict])
def search_images(kw: str, db: Session = Depends(get_db)):
    images = crud.Images.search(db, kw)
    return [i.json for i in images]


def get_image_folder_cover(id_: int, db: Session):
    cover = crud.ImagesFolder.get_cover(db, id_)
    if cover is None:
        return "/static/images/folder_cover.webp"
    else:
        return f"/static/images/{cover.folder}/{cover.hash}.webp"


# 获取所有目录
@router.get("/folder/all", response_model=List[dict])
def gets(db: Session = Depends(get_db)):
    folder = crud.ImagesFolder.gets(db)

    data: List[dict] = []
    for item in folder:
        data.append({
            "id": item.id,
            "name": item.name,
            "created_time": item.created_time,
            "updated_time": item.updated_time,
            "description": item.description,
            "count": item.count,
            "size": item.size,
            "operable": item.operable,
            "cover": get_image_folder_cover(item.id, db)
        })

    return JSONResponse(data)


# 获取目录
@router.get("/folder/{id}", response_model=dict)
def get(id: int, db: Session = Depends(get_db)):
    folder = crud.ImagesFolder.get(db, id)
    if folder is None:
        raise HTTPException(message=f"找不到ID为[{id}]的目录")
    else:
        return JSONResponse({
            "id": folder.id,
            "name": folder.name,
            "description": folder.description,
            "created_time": folder.created_time,
            "updated_time": folder.updated_time,
            "count": folder.count,
            "size": folder.size,
            "operable": folder.operable,
            "cover": get_image_folder_cover(folder.id, db)
        })


# 创建目录
@router.post("/folder", response_model=ImageFolder)
def create(data: CreateImageFolder, db: Session = Depends(get_db)):
    if crud.ImagesFolder.get_by_name(db, data.name) is None:
        folder = crud.ImagesFolder.create(db, data)
        utils.files.mkdir(FilePath.IMAGES / str(folder.id))
        utils.files.mkdir(FilePath.IMAGES / f"{folder.id}/thumb")
        return folder
    else:
        raise HTTPException(message="已存在相同名称的目录")


# 修改目录名称
@router.put("/folder", response_model=ImageFolder)
def update(data: UpdateImageFolder, db: Session = Depends(get_db)):
    target_folder = crud.ImagesFolder.get(db, data.id)
    if target_folder is None:
        raise HTTPException(message=f"找不到ID为[{data.id}]的目录")
    elif target_folder.operable:
        same_folder = crud.ImagesFolder.get_by_name(db, data.name)
        if same_folder is not None and same_folder.id != target_folder.id:
            raise HTTPException(message="已存在相同名称的目录")

        return crud.ImagesFolder.update(db, data)
    else:
        raise HTTPException(http_code=403, message=f"禁止修改[{target_folder.name}]目录")


# 删除目录
@router.delete("/folder/{id}")
def delete(id: int, force: Optional[bool] = False, db: Session = Depends(get_db)):
    folder = crud.ImagesFolder.get(db, id)

    if folder is None:
        raise HTTPException(message=f"找不到ID为[{id}]的目录")
    elif folder.operable:
        if force:
            utils.files.rmdir(FilePath.IMAGES / str(folder.id))
            return crud.ImagesFolder.delete(db, id)
        else:
            # TODO: 普通删除需要进行使用检查
            utils.files.rmdir(FilePath.IMAGES / str(folder.id))
            return crud.ImagesFolder.delete(db, id)
    else:
        raise HTTPException(http_code=403, message=f"禁止删除[{folder.name}]目录")
